import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
df=pd.read_csv('H:\VS Code\淘宝用户购物行为数据可视化分析\淘宝用户购物行为数据集_清洗后.csv')
#转化阶段定义（可配置）
funnel_steps= [
    {'name':'浏览','type':1,'color':'#636EFA'},
    {'name':'收藏','type':2,'color':'#EF553B'},
    {'name':'加购物车','type':3,'color':'#00CC96'},
    {'name':'购买','type':4,'color':'#AB63FA'}
]
##创建阶段标识字段
for step in funnel_steps:
    df[step['name']]=(df['行为类型']==step['type']).astype(int)


#计算用户的转换路径
##构建用户行为序列
user_journey=df.sort_values(['用户ID','时间']).groupby('用户ID').agg(
    行为序列=('行为名称',list),
    是否购买=('行为类型',lambda x:4 in x)
)
##标记转化阶段
def mark_steps(actions):
    steps=[]
    for action in actions:
        if action =='浏览' and '浏览' not in steps:
            steps.append('浏览')
        elif action=='收藏'and '收藏' not in steps:
            steps.append('收藏')
        elif action== '加购物车' and '加购物车' not in steps:
            steps.append('加购物车')
        elif action =='购买' and '购买' not in steps:
            steps.append('购买')
            break
    return steps
user_journey['转化路径']=user_journey['行为序列'].apply(mark_steps)



#计算漏斗指标
##各阶段用户数计算
funnel_data=[]
# 根据转化路径统计各阶段真实用户数
def get_funnel_users(user_paths, step_index):
    # 筛选完成到当前步骤的用户
    return sum(
        1 for path in user_paths 
        if len(path) > step_index
    )

# 重新计算各阶段用户数
funnel_data = []
for i, step in enumerate(funnel_steps):
    user_count = get_funnel_users(user_journey['转化路径'], i)
    funnel_data.append({
        '阶段': step['name'],
        '用户数': user_count
    })

funnel_df = pd.DataFrame(funnel_data)

#转化率计算
# 计算转化率（基于上一步用户数）
funnel_df['转化率'] = funnel_df['用户数'] / funnel_df['用户数'].shift(1).fillna(funnel_df['用户数'].iloc[0])
funnel_df['流失率'] = 1 - funnel_df['转化率']
print(funnel_df)



#可视化漏斗建立
fig1 = go.Figure(go.Funnel(
    y=funnel_df['阶段'],
    x=funnel_df['用户数'],
    textposition="inside",
    textinfo="value+percent previous",  # 显示绝对值和相对上一步转化率
    marker={'color': [step['color'] for step in funnel_steps]},
    connector={'line': {'color': "royalblue", 'width': 2}}
))
##添加流失率注释
# 创建阶段顺序映射
stage_order = {step['name']: idx for idx, step in enumerate(funnel_steps)}

# 添加y轴坐标字段
funnel_df['y_pos'] = funnel_df['阶段'].map(stage_order)

# 修改注释代码
for i in range(1, len(funnel_df)):
    fig1.add_annotation(
        x=funnel_df['用户数'][i-1] * 0.7,
        y=(funnel_df['y_pos'][i-1] + funnel_df['y_pos'][i]) / 2,  # 使用数值坐标
        text=f"流失率: {funnel_df['流失率'][i]:.1%}",
        showarrow=False,
        font={'color': 'red'},
        yanchor='middle'
    )
fig1.update_layout(
    title='用户行为转化漏斗',
    funnelmode="stack",
    showlegend=False,
    margin={'l': 150}  # 留出左侧空间
)
fig1.show()


#深度分析
##分析加购物车未购买的用户
cart_no_buy_users= df[
    (df['加购物车'] == 1) &  # 加购行为
    (~df['用户ID'].isin(user_journey[user_journey['是否购买']].index))  # 未购买用户
]['用户ID'].unique()

##特征分析
cart_abandon_stats = df[df['用户ID'].isin(cart_no_buy_users)].groupby('用户ID').agg(
    加购物车次数=('加购物车', 'sum'),
    最后加购物车时间=('时间', 'max'),
    浏览商品数=('商品ID', 'nunique')
)

##时间敏感性分析
now=pd.to_datetime('2014-12-18')
cart_abandon_stats['最后加购物车时间'] = pd.to_datetime(cart_abandon_stats['最后加购物车时间'])
cart_abandon_stats['加购物车天数']=(now-cart_abandon_stats['最后加购物车时间']).dt.days
print(cart_abandon_stats['加购物车天数'].describe())



#流失用户直方图
# 创建独立的直方图对象
fig_hist = make_subplots(
    rows=2, cols=2,
    subplot_titles=('加购次数分布', '加购后天数分布', '浏览商品数分布'),
    vertical_spacing=0.15
)

features = [
    {'col': '加购物车次数', 'bin_size': 1, 'row': 1, 'col_pos': 1},
    {'col': '加购物车天数', 'bin_size': 1, 'row': 1, 'col_pos': 2},
    {'col': '浏览商品数', 'bin_size': 5, 'row': 2, 'col_pos': 1}
]

for feat in features:
    fig_hist.add_trace(
        go.Histogram(
            x=cart_abandon_stats[feat['col']],
            nbinsx=20,
            name=feat['col'],
            marker_color='#636EFA',
            opacity=0.7,
            hovertemplate=f"{feat['col']}: %{{x}}<br>用户数: %{{y}}<extra></extra>"
        ),
        row=feat['row'], 
        col=feat['col_pos']
    )

fig_hist.update_layout(
    title_text='流失用户特征分布分析（加购未购买）',
    height=600,
    margin=dict(t=80, b=120),
    annotations=[...]  # 保留原有注释配置
)
fig1.show()
fig_hist.show()
